﻿using System;
using System.Diagnostics;
using DotNetRepository.Cache;

namespace DotNetRepository.LinqToSql
{
    /// <summary>
    /// Custom ICacheItemExpiration similar to a Sliding Expiration except that the expiration period grows each 
    /// time the object is accessed, up to the MaxExpiration as defined in app.config.
    /// </summary>
    internal class EntityCacheItemExpiration<TEntity> : ICacheItemExpiration<Guid,EntityCacheItem<TEntity>> 
        where TEntity : class, IEntity
    {
        private TimeSpan _expiration;
        private bool _hasExpired;
        private CacheItem<Guid,EntityCacheItem<TEntity>> _item;

        private static TimeSpan InitialExpiration
        {
            get { return Settings.Default.DefaultCacheInitialExpiration; }
        }
        private static TimeSpan MaxExpiration
        {
            get { return Settings.Default.DefaultCacheMaxExpiration; }
        }
        
        #region ICacheItemExpiration Members

        public bool HasExpired()
        {
            if (_item != null)
            {
                // Don't expire dirty items, even if time has elapsed.
                var cacheItem = _item.Value;
                if (cacheItem != null)
                {
                    if (cacheItem.Context.GetChangeSet().Updates.Count > 0)
                        return false;
                }

                var currentTime = DateTime.Now;

                var timeElapsed = currentTime.Subtract(_item.LastAccessedTime);
                if (timeElapsed >= _expiration)
                {
                    _hasExpired = true;

                    if (Settings.Default.EnableCacheTrace)
                        Debug.WriteLine(String.Format("{0} expired. {1} of {2} sec elapsed.",
                            _item.Value, (long) timeElapsed.TotalSeconds,
                            (long) _expiration.TotalSeconds));
                }
            }
            return _hasExpired;
        }

        public void Initialize(CacheItem<Guid, EntityCacheItem<TEntity>> owningCacheItem)
        {
            _item = owningCacheItem;
            _expiration = InitialExpiration;
        }

        public void ItemTouched()
        {
            if (!_hasExpired)
            {
                if (_expiration.TotalSeconds == 0)
                {
                    _expiration = InitialExpiration;
                }
                else
                {
                    if (_expiration < MaxExpiration)
                    {
                        TimeSpan timeElapsed = DateTime.Now.Subtract(_item.LastAccessedTime);

                        if (timeElapsed.TotalSeconds >= _expiration.TotalSeconds/2)
                        {
                            var newExpiration = new TimeSpan(timeElapsed.Ticks*2);
                            _expiration = newExpiration < MaxExpiration
                                              ? newExpiration
                                              : MaxExpiration;
                        }
                    }

                    _item.LastAccessedTime = DateTime.Now;

                    if (Settings.Default.EnableCacheTrace)
                        Debug.WriteLine(String.Format("{0} touched. Will expire in {1} sec.",
                            _item.Value, (long)_expiration.TotalSeconds));
                }
            }
        }

        #endregion
    }
}